﻿#region 참조 네임스페이스

using Skying.ComponentModel;
using System;
using System.Drawing;
using System.IO;
using System.Net;
using System.Text;
using System.Windows;
using System.Windows.Media.Imaging;

#endregion

namespace Skying.IO
{
	public class MJpegDecoder
	{
		#region 상수

		/// <summary>
		/// 
		/// </summary>
		private const int _CHUNK_SIZE = 1024;

		/// <summary>
		/// 
		/// </summary>
		private readonly byte[] _JPEG_HEADER = new byte[] { 0xFF, 0xD8 };

		#endregion

		#region 변수

		/// <summary>
		/// 
		/// </summary>
		private bool _streamActive;

		#endregion

		#region 프로퍼티

		/// <summary>
		/// 
		/// </summary>
		public byte[] CurrentFrame
		{
			get;
			private set;
		}

		/// <summary>
		/// 
		/// </summary>
		public Bitmap Bitmap
		{
			get;
			private set;
		}

		/// <summary>
		/// 
		/// </summary>
		public BitmapImage BitmapImage
		{
			get;
			private set;
		}

		/// <summary>
		/// 
		/// </summary>
		public Action<AsyncResultEventArgs<MJpegFrameReadyEventArgs>> FrameReady
		{
			get;
			set;
		}

		/// <summary>
		/// 
		/// </summary>
		public Action<AsyncResultEventArgs<Exception>> Error
		{
			get;
			set;
		}

		#endregion

		#region 생성자/소멸자

		public MJpegDecoder()
		{
			this.BitmapImage = new BitmapImage();
		}

		#endregion

		#region 요청

		/// <summary>
		/// 
		/// </summary>
		/// <param name="uri"></param>
		/// <param name="userName"></param>
		/// <param name="password"></param>
		public void Start(Uri uri, string userName = null, string password = null)
		{
			HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);

			if (string.IsNullOrEmpty(userName) || !string.IsNullOrEmpty(password))
			{
				request.Credentials = new NetworkCredential(userName, password);
			}

			request.BeginGetResponse((asyncResult) =>
			{
				try
				{
					HttpWebResponse response = request.EndGetResponse(asyncResult) as HttpWebResponse;

					string boundary = response.Headers["Content-Type"].Split('=')[1].Replace("\"", string.Empty);
					byte[] boundaryBytes = Encoding.UTF8.GetBytes(boundary.StartsWith("--") ? boundary : string.Format("--{0}", boundary));

					Stream stream = response.GetResponseStream();

					BinaryReader reader = new BinaryReader(stream);

					this._streamActive = true;

					byte[] buffer = reader.ReadBytes(_CHUNK_SIZE);

					byte[] imageBuffer = new byte[1024 * 1024];

					while (this._streamActive)
					{
						// JPEG 이미지 포맷의 헤더를 검색한다.
						int start = buffer.Find(_JPEG_HEADER);

						// 헤더가 존재할 경우
						if (start != -1)
						{
							int size = buffer.Length - start;

							Array.Copy(buffer, start, imageBuffer, 0, size);

							while (true)
							{
								buffer = reader.ReadBytes(_CHUNK_SIZE);

								int end = buffer.Find(boundaryBytes);

								if (end != -1)
								{
									Array.Copy(buffer, 0, imageBuffer, size, end);

									size += end;

									byte[] frame = new byte[size];

									Array.Copy(imageBuffer, 0, frame, 0, size);

									if (Application.Current != null)
									{
										this.BitmapImage = new BitmapImage();

										this.BitmapImage.BeginInit();

										this.BitmapImage.StreamSource = new MemoryStream(frame);

										this.BitmapImage.EndInit();

										Synchronization.Synchronize(this.FrameReady, new AsyncResultEventArgs<MJpegFrameReadyEventArgs>(new MJpegFrameReadyEventArgs(frame, this.BitmapImage)));
									}
									else
									{
										this.Bitmap = new Bitmap(new MemoryStream(frame));

										Synchronization.Synchronize(this.FrameReady, new AsyncResultEventArgs<MJpegFrameReadyEventArgs>(new MJpegFrameReadyEventArgs(frame, this.Bitmap)));
									}

									Array.Copy(buffer, end, buffer, 0, buffer.Length - end);

									byte[] temp = reader.ReadBytes(end);

									Array.Copy(temp, 0, buffer, buffer.Length - end, temp.Length);

									break;
								}

								Array.Copy(buffer, 0, imageBuffer, size, buffer.Length);

								size += buffer.Length;
							}
						}
					}
				}
				catch (Exception error)
				{
					Synchronization.Synchronize(this.Error, new AsyncResultEventArgs<Exception>(error));
				}
			}, null);
		}

		/// <summary>
		/// 
		/// </summary>
		public void Stop()
		{
			this._streamActive = false;
		}

		#endregion
	}
}